/**
	License:		Artistic license 2.0 (see license.txt, also available online at http://www.perlfoundation.org/artistic_license_2_0)
	Authors:		OrbitalLab (http://www.orbitallab.ru/moonglide/), 2007-2009

	File:			LinuxOS.d
	Description:	linux 32bit routines' class
	Date:			11.08.2008 by Digited
*/
module moonglide.os.linux.LinuxOS;

version( linux )
{

import
	moonglide.os.IOS,

	X11.Xlib,
	X11.Xatom,
	X11.Xutil,
	X11.keysymdef,

	derelict.opengl.gl,
	derelict.opengl.glu,
	derelict.opengl.glx,

	moonglide.io.LogSystem,
	moonglide.math.MoonMath,
	moonglide.util.MoonUtil;

alias derelict.opengl.glx	dglx;
alias X11.Xlib.Display		xdisp;
alias X11.Xlib.Window		xwind;

package
{
	xdisp*			g_pDisplayX	= null;
	dglx.Display*	g_pDisplayGL= null;
	xwind			g_window;
	GLXContext		glxContext;
}

//=============================================================
/// class to create an X Window and setup OpenGL on linux
//=============================================================
class CLinuxOS : IOS
{
	mixin TEventSender;

	protected:
		bool		_windowcreated,
					_terminated,
					_running,
					_fullscreen,
					_cursorGrabbed,
					_keyboardGrabbed;

		Atom		_wmDeleteWindow;

		CEvent		_event;

		char[]		_windowCaption = "";

		this()
		{
			_id = "LinuxOS";
			_event = new CEvent;
		}


		//=============================================================
		/// create null cursor for fullscreen mode
		//=============================================================
		Cursor _CreateNULLCursor( xdisp* display, xwind root )
		{
			X11.X.Pixmap	cursormask;
			XGCValues		xgc;
			_XGC*			gc;
			XColor			col;
			Cursor			cursor;

			cursormask = XCreatePixmap( display, cast(Drawable)root, 1, 1, 1 );
			xgc.c_function = GXclear;
			gc = XCreateGC( display, cast(Drawable)cursormask, GCFunction, &xgc );
			XFillRectangle( display, cast(Drawable)cursormask, gc, 0, 0, 1, 1 );
			col.pixel = 0;
			col.red = 0;
			col.flags = 4;
			cursor = XCreatePixmapCursor( display, cursormask, cursormask,
				&col,&col, 0,0 );
			XFreePixmap( display, cursormask );
			XFreeGC( display, gc );

			return cursor;
		}

		static CLinuxOS singleton;

	public:
		static CLinuxOS opCall()
		{
			if( singleton is null )
				singleton = new CLinuxOS;

			return singleton;
		}

		~this()
		{
			delete _event;
			shutdown;
		}


		bool createPlatformWindow( inout Size2ui dimensions, bool fullscreen, char[] name )
		{
			if( _windowcreated )
			{
				errorLog( "CLinuxOS: trying to create another window!" );
				return false;
			}

			debug debugLog.trace( "CLinuxOS module: Starting window creation" );

			XSetWindowAttributes	windowAttributes;
			dglx.XVisualInfo*		visualInfo = null;
			XEvent					event;
			Colormap				colorMap;

			int						errorBase,
									eventBase;

			_fullscreen = fullscreen;

			// Open a connection to the X server
			g_pDisplayX = XOpenDisplay( null );

			if( !g_pDisplayX )
			{
				errorLog( "could not open display" );
				return false;
			}

			g_pDisplayGL = cast( dglx.Display* )g_pDisplayX;

			// Make sure OpenGL's GLX extension supported
			if( !glXQueryExtension( g_pDisplayGL, &errorBase, &eventBase ))
			{
				errorLog( "X server has no OpenGL GLX extension" );
				return false;
			}

			_wmDeleteWindow = XInternAtom( g_pDisplayX, "WM_DELETE_WINDOW", false );

			// Find an appropriate visual

			int[] doubleBufferVisual  =
			[
				GLX_RGBA,			// Needs to support OpenGL
				GLX_DEPTH_SIZE, 16,	// Needs to support a 16 bit depth buffer
				GLX_DOUBLEBUFFER,	// Needs to support double-buffering
				0					// end of list
			];

			// Try for the double-bufferd visual first
			visualInfo = glXChooseVisual( g_pDisplayGL, XDefaultScreen( g_pDisplayX ),
				doubleBufferVisual.ptr );

			if( !visualInfo )
			{
				errorLog( "no RGB visual with depth buffer" );
				return false;
			}

			// Create an OpenGL rendering context
			glxContext = glXCreateContext(
				g_pDisplayGL,
				visualInfo,
				null,      // No sharing of display lists
				GL_TRUE ); // Direct rendering if possible

			if( !glxContext )
			{
				errorLog( "could not create rendering context" );
				return false;
			}

			// Create an X colormap since we're probably not using the default visual
			colorMap = XCreateColormap(
				g_pDisplayX,
				XRootWindow( g_pDisplayX, visualInfo.screen ),
				cast( _N4* )visualInfo.visual,
				AllocNone );

			windowAttributes.colormap     = colorMap;
			windowAttributes.border_pixel = 0;
			windowAttributes.event_mask   =	ExposureMask           |
											VisibilityChangeMask   |
											KeyPressMask           |
											KeyReleaseMask         |
											ButtonPressMask        |
											ButtonReleaseMask      |
											PointerMotionMask      |
											StructureNotifyMask    |
											SubstructureNotifyMask |
											FocusChangeMask;

			// Create an X window with the selected visual
			g_window = XCreateWindow(
				g_pDisplayX,
				XRootWindow( g_pDisplayX, visualInfo.screen ),
				0, 0,     // x/y position of top-left outside corner of the window
				dimensions.x, dimensions.y, // Width and height of window
				0,        // Border width
				visualInfo.depth,
				InputOutput,
				cast( _N4* )visualInfo.visual,
				CWBorderPixel | CWColormap | CWEventMask,
				&windowAttributes );

			// Allow us to trap the Window Close protocol
    		XSetWMProtocols( g_pDisplayX, g_window, &_wmDeleteWindow, 1 );

			// Bind the rendering context to the window
			glXMakeCurrent( g_pDisplayGL, cast(GLXDrawable)g_window, glxContext );

			// Request the X window to be displayed on the screen
			XMapWindow( g_pDisplayX, g_window );

			if( fullscreen )
			{
				XDefineCursor( g_pDisplayX, g_window,
					_CreateNULLCursor( g_pDisplayX, g_window ) );

				freezeCursor( true );

				_keyboardGrabbed = XGrabKeyboard( g_pDisplayX, g_window, true,
					GrabModeAsync, GrabModeAsync, 0 ) == GrabSuccess;
			}

			_windowcreated = true;
			_terminated = false;

			setWindowCaption( name );

			debug debugLog.trace( "window successfully created." );

			return true;
		}

		//============================================================
		/// grab or ungrab mouse cursor
		//============================================================
		void freezeCursor( bool freeze )
		{
			if( freeze )
				_cursorGrabbed = XGrabPointer(
					g_pDisplayX, g_window, true,
					ButtonPressMask | ButtonReleaseMask |
					PointerMotionMask, GrabModeAsync, GrabModeAsync,
					g_window, 0, 0 ) == GrabSuccess;
			else
				if( _cursorGrabbed )
				{
					XUngrabPointer( g_pDisplayX, 0 );
					_cursorGrabbed = false;
				}
		}

		//=============================================================
		/// get window caption
		//=============================================================
		void setWindowCaption( char[] caption, bool store = true )
		{
			if( _windowcreated )
			{
				caption ~= '\0';
				XStoreName( g_pDisplayX, g_window, caption.ptr );
    			XSetIconName( g_pDisplayX, g_window, caption.ptr );

    			if( store )
    				_windowCaption = caption;
			}
		}


		//=============================================================
		/// get window caption
		//=============================================================
		char[] getWindowCaption()
		{
			return _windowCaption;
		}


		//===============================================================
		/// ChangeScreenResolution - todo
		//===============================================================
		bool changeScreenResolution( uint width, uint height, ubyte bpp,
			bool fullscreen, ushort RefreshFrequency )
		{
			return true;
		}

		void checkKeyStates()
		{
			// TODO: implement
		}

		bool getKeyState( uint index )
		{
			// TODO: implement
			return false;
		}

		//===============================================================
		/// system loop code
		//===============================================================
		bool runLoop()
		{
			static int key;
			static _XEvent systemEvent;

			while( XPending( g_pDisplayX ) ) // Loop to compress events
			{
				XNextEvent( g_pDisplayX, &systemEvent );

				switch( systemEvent.type )
				{
					case KeyPress:
						// Now try pimary keysym
						key = XKeycodeToKeysym( g_pDisplayX, systemEvent.xkey.keycode, 0 );

						_event.type = eeventtype.system;
						_event.subtype = esystemevent.kbkey;
						_event.ints[ "kb_key" ] = key;
						_event.bools[ "kb_pressed" ] = true;
						sendEvent( _event );
						break;

					case KeyRelease:
						// Now try pimary keysym
						key = XKeycodeToKeysym( g_pDisplayX, systemEvent.xkey.keycode, 0 );

						_event.type = eeventtype.system;
						_event.subtype = esystemevent.kbkey;
						_event.ints[ "kb_key" ] = key;
						_event.bools[ "kb_pressed" ] = false;
						sendEvent( _event );
						break;

					case ButtonPress:
					case ButtonRelease:
						switch( systemEvent.xbutton.button )
						{
							case Button1:	// left mouse button
								_event.type = eeventtype.system;
								_event.subtype = esystemevent.msbutton;
								_event.ints[ "ms_button" ] = 0;
								_event.bools[ "ms_pressed" ] = systemEvent.type == ButtonPress;
								sendEvent( _event );
								break;

							case Button2:	// middle
								_event.type = eeventtype.system;
								_event.subtype = esystemevent.msbutton;
								_event.ints[ "ms_button" ] = 1;
								_event.bools[ "ms_pressed" ] = systemEvent.type == ButtonPress;
								sendEvent( _event );
								break;

							case Button3:	// right
								_event.type = eeventtype.system;
								_event.subtype = esystemevent.msbutton;
								_event.ints[ "ms_button" ] = 2;
								_event.bools[ "ms_pressed" ] = systemEvent.type == ButtonPress;
								sendEvent( _event );
								break;

							case Button4:	// mouse wheel up
								_event.type = eeventtype.system;
								_event.subtype = esystemevent.mswheel;
								_event.ints[ "ms_delta" ] = +1;
								sendEvent( _event );
								break;

							case Button5:	// mouse wheel down
								_event.type = eeventtype.system;
								_event.subtype = esystemevent.mswheel;
								_event.ints[ "ms_delta" ] = -1;
								sendEvent( _event );
								break;

							default:
								break;
						}

						break;

					case MotionNotify:
						static Point2i lastpnt;
						static bool skipSecondMoveEvent;

						if( skipSecondMoveEvent )
						{
							skipSecondMoveEvent = false;
							break;
						}

						_event.type = eeventtype.system;
						_event.subtype = esystemevent.msmove;

						_event.ints[ "ms_x" ] = systemEvent.xmotion.x - lastpnt.x;
						_event.ints[ "ms_y" ] = systemEvent.xmotion.y - lastpnt.y;

						if( _cursorGrabbed )
						{
							skipSecondMoveEvent = true;
							XWarpPointer( g_pDisplayX, 0, g_window, 0,0,0,0, lastpnt.x, lastpnt.y );
						}
						else
							lastpnt.set( systemEvent.xmotion.x, systemEvent.xmotion.y );

						sendEvent( _event );
						break;

					// resize
					case ConfigureNotify:
						_event.type = eeventtype.program;
						_event.subtype = eprogramevent.resize;
						_event.ints[ "size_x" ] = systemEvent.xconfigure.width;
						_event.ints[ "size_y" ] = systemEvent.xconfigure.height;
						sendEvent( _event );
						break;

					// destroy
					case DestroyNotify:
						return false;

					// closed by the window manager
					case ClientMessage:
						if( cast(Atom) systemEvent.xclient.data.l[ 0 ] == _wmDeleteWindow )
							return false;

					// mapped (un-iconified)
					case MapNotify:
						//
						break;

					// unmapped (iconified)
					case UnmapNotify:
						//
						break;

					// activated
					case FocusIn:
						//
						break;

					// de-activated
					case FocusOut:
						//
						break;

					// window contents damaged
					case Expose:
						//
						break;

					default:
						break;
				}
			}

			return true;
		}

		void shutdown()
		{
			if( _terminated )
			{
				errorLog( "LinuxOS: shutdown called while already terminated!" );
				return;
			}

			if( !_windowcreated )
			{
				errorLog( "LinuxOS: shutdown called while window wasn't created!" );
				return;
			}

			debug debugLog.trace( "LinuxOS shutdown started" );

			if( _fullscreen )
			{
				if( _cursorGrabbed )
					XUngrabPointer( g_pDisplayX, 0 );

				if( _keyboardGrabbed )
					XUngrabKeyboard( g_pDisplayX, 0 );

				XUndefineCursor( g_pDisplayX, g_window );
			}

			glXMakeCurrent( g_pDisplayGL, 0, null );		// Release the context
			glXDestroyContext( g_pDisplayGL, glxContext );

			XUnmapWindow( g_pDisplayX, g_window );		// unmap the window
			XDestroyWindow( g_pDisplayX, g_window );	//destroy the window
			XCloseDisplay( g_pDisplayX );				//close the display

			debug debugLog.trace( "LinuxOS shutdown ended" );

			_windowcreated = false;
			_terminated = true;
		}
} // end class CLinuxOS

} // end version( linux )

